//=============================================================================
//
//   Copyright (C) 2020 All rights reserved.
//
//   Filename:		mod.rs
//
//   Author: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Date: 2020-10-30 13:17
//
//   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Last modified:	2020-11-06 20:37
//
//   Description:
//
//=============================================================================
use crate::dimension::{DimType, HasDimension};
use crate::geometry::point::{Linked2PointPool, Point, PointPoolLike};
use crate::util::{Hashable, Indexable};

pub trait ShapeLike<DT: DimType>:
    Hashable<String> + HasDimension + Indexable + PointPoolLike<DT> + Linked2PointPool<DT>
{
    /// get point list
    fn get_point_indices(&self) -> &[usize];
    /// get mutable point list
    fn get_point_indices_mut(&mut self) -> &mut [usize];

    #[inline]
    fn get_point_index(&self, index: usize) -> usize {
        match self.get_point_indices().get(index) {
            Some(v) => *v,
            None => panic!(
                "Invalid point index #{} in shape {}",
                index,
                self.get_hashkey()
            ),
        }
    }
    /// get the first boundary point
    #[inline]
    fn get_head_point(&self) -> &Point<DT::Dim> {
        let index = self
            .get_point_indices()
            .first()
            .expect("Failed to get first point in shape");
        self.get_point_pool().get_point(*index)
    }
    /// get the last boundary point
    #[inline]
    fn get_tail_point(&self) -> &Point<DT::Dim> {
        let index = self
            .get_point_indices()
            .last()
            .expect("Failed to get last point in shape");
        self.get_point_pool().get_point(*index)
    }
    /// get points's indices list
    #[inline]
    fn get_pt_list(&self) -> Vec<&Point<DT::Dim>> {
        let mut res = Vec::<&Point<DT::Dim>>::new();
        for index in 0..self.get_point_num() {
            res.push(self.get_point(index))
        }
        res
    }
}

#[cfg(test)]
mod shape_interface_impl_macro {
    use super::*;
    use crate::dimension::PlaneStrain;
    use crate::geometry::point::tests::SomePointPool;
    use crate::geometry::point::PointLike;
    use crate::util::Hashable;
    use nalgebra::dimension::{Dim, U2};
    use std::rc::Rc;

    shape_interface_impl!(Triangle<DT: DimType>);

    #[derive(Debug)]
    struct Triangle<DT: DimType> {
        id: Option<usize>,
        pt_list: [usize; 3],
        pool: Option<Rc<dyn PointPoolLike<DT>>>,
    }
    impl<DT: DimType> HasDimension for Triangle<DT> {
        fn get_dim(&self) -> usize {
            2usize
        }
        fn get_topo_dim(&self) -> usize {
            DT::Dim::try_to_usize().expect("Failed to usize dimension")
        }
    }

    #[test]
    fn shape_constructor() {
        let mut spp = SomePointPool::<PlaneStrain>::default();
        let mut p1 = Point::<U2>::new(0f32, 0f32);
        p1.set_index(1);
        spp.append_point(p1);
        let mut p2 = Point::<U2>::new(1f32, 0f32);
        p2.set_index(8);
        spp.append_point(p2);
        let mut p3 = Point::<U2>::new(1f32, 1f32);
        p3.set_index(7);
        spp.append_point(p3);
        let mut p4 = Point::<U2>::new(0f32, 1f32);
        p4.set_index(3);
        spp.append_point(p4);

        let mut tri = Triangle::<PlaneStrain> {
            id: None,
            pt_list: [1, 8, 7],
            pool: None,
        };
        tri.set_point_pool(Rc::new(spp));

        assert_eq!(tri.get_head_point().as_np().x, 0f32);
        assert_eq!(tri.get_head_point().as_np().y, 0f32);
        assert_eq!(tri.get_tail_point().as_np().x, 1f32);
        assert_eq!(tri.get_tail_point().as_np().y, 1f32);
        assert_eq!(tri.get_point(0).as_np().x, 0f32);
        assert_eq!(tri.get_point(0).as_np().y, 0f32);
        let id_list = tri.get_point_indices();
        assert_eq!(id_list, vec![1, 8, 7]);
        assert_eq!("[1,7,8]", tri.get_hashkey());
    }
    #[test]
    fn partial_eq_and_hash() {
        let mut spp = SomePointPool::<PlaneStrain>::default();
        let mut p1 = Point::<U2>::new(0f32, 0f32);
        p1.set_index(1);
        spp.append_point(p1);
        let mut p2 = Point::<U2>::new(1f32, 0f32);
        p2.set_index(8);
        spp.append_point(p2);
        let mut p3 = Point::<U2>::new(1f32, 1f32);
        p3.set_index(7);
        spp.append_point(p3);
        let mut p4 = Point::<U2>::new(0f32, 1f32);
        p4.set_index(3);
        spp.append_point(p4);
        let mut tri1 = Triangle::<PlaneStrain> {
            id: None,
            pt_list: [1, 8, 7],
            pool: None,
        };
        let ptr_spp = Rc::new(spp);
        tri1.set_point_pool(ptr_spp.clone());
        let mut tri2 = Triangle::<PlaneStrain> {
            id: None,
            pt_list: [7, 1, 8],
            pool: None,
        };
        tri1.set_point_pool(ptr_spp.clone());

        assert!(tri1 == tri2);
        assert_eq!(tri1, tri2);
        // let set = std::collections::HashSet::<Rc<dyn ShapeLike<PlaneStrain>>>::new();
        // set.insert(Rc::new(tri1));
    }
}
